package org.aspectsense.middleware.adaptation;

import java.util.Iterator;
import java.util.LinkedList;

import org.aspectsense.middleware.ComponentContainer;
import org.aspectsense.middleware.Repository;
import org.aspectsense.middleware.ServiceWrapper;

public final class AdaptationManager {

	static LinkedList <BigraphAdaptationTree> provisionTrees = new LinkedList <BigraphAdaptationTree>(); 
	static LinkedList <LinkedList<BigraphComponentNode>> maxProvisionTrees = new LinkedList <LinkedList<BigraphComponentNode>>(); 
	//---------Thread-safe Singleton For Ensuring single instantiation of Repository
	public final static AdaptationManager INSTANCE = new AdaptationManager();
	private AdaptationManager()
	{
		// Exists only to defeat instantiation.
	}
	//---------------------------------------

	public enum EVENT_TYPE {SERVICE_REG, SERVICE_UNREG}
	public static void notify (EVENT_TYPE type, Object from)
	{
		switch (type){
		
			case SERVICE_REG:
				adapt ();
				break;
			case SERVICE_UNREG:
				adapt ();
				break;
			
			default: System.out.println("Unknown event");break;
        }

	}
	
	public static void adapt ()
	{
		evaluateComponents();
		
		AdaptationGraph.setGuiTree();
		if (provisionTrees!=null && provisionTrees.size()>0)
		{
			evaluateServices();	
			AdaptationGraph.setMaxGuiTree();
			
		}
		
	}
	
	/**
	 * Create clones of the provision trees that contain single provision nodes per required service.
	 * The nodes that maximize utility will be selected for each required service.
	 */
	private static void evaluateServices() 
	{
		BigraphComponentNode currNode=null, parent=null;
		LinkedList <BigraphComponentNode> chains, siblings, maxChain;
		ServiceWrapper sw;
		double maxUtil=0.0, currUtil=0.0;
		System.out.println ("evaluateServices()");
		maxProvisionTrees.clear();
		for (BigraphAdaptationTree tree: provisionTrees)
		{
			chains = tree.getTree();
			
			maxChain = new LinkedList <BigraphComponentNode> ();
			BigraphComponentNode maxNode = null;
			for (int i=chains.size()-1; i>=0; i--)
			{
				currNode = chains.get(i);
				System.out.println ("Tree node:"+currNode.getShortComponentName());
				parent = currNode.getParent();
				if (parent==null) //root
				{
					
					//Compute the end-user service utility 
					//and add it to the maxProvisionTrees
					sw = Repository.getServiceWrapper(currNode.getProvidedService(), currNode.getContainer());
					assert (sw.getUtility()>0);
					
					maxChain.add(currNode);
					System.out.println (currNode.getShortComponentName()+" is the root and has utility: "+sw.getUtility());
					maxProvisionTrees.add(maxChain);
				}
				else
				{
					siblings = getSiblings (chains, currNode);
					//Iterate siblings and add to the
					//maxProvisionTrees the one with the
					//maximum utility
					System.out.println (currNode.getShortComponentName()+" has the following siblings:");
					maxNode=currNode;
					for (BigraphComponentNode n:siblings)
					{
					
						sw = Repository.getServiceWrapper(n.getProvidedService(), n.getContainer());
						currUtil = sw.getUtility();
						System.out.println (n.getShortComponentName()+" with utility:"+currUtil);
						if (currUtil<0)
						{
							System.out.println ("ERROR in AdaptationManager, could not resolve utility function");
						}
						if (currUtil > maxUtil)
						{
							maxUtil = currUtil;
							maxNode = n;
						}
					}
					if (maxNode==null)
					{
						System.out.println ("NULL NODE 2!!!!!!!!!!");
					}
					
					maxChain.add(maxNode);
				}
			}
		}
		evaluateEndUserServices();
		
		
	}

	/**
	 * In case more than one provider exists for 
	 * an end-user service we have to choose the one with 
	 * the best utility and remove the others from the
	 * maxProvisionTrees
	 */
	private static void evaluateEndUserServices() 
	{
		
		for (LinkedList<BigraphComponentNode> maxchain:maxProvisionTrees)
		{
			System.out.println ("is the first in the list Root?:"+maxchain.getFirst().isRoot());
		}
		
	}

	/**
	 * 
	 * @param chains
	 * @param parent
	 * @return A list with tree nodes that have the same parent
	 */
	private static LinkedList<BigraphComponentNode> getSiblings(LinkedList<BigraphComponentNode> chains, BigraphComponentNode currNode) 
	{
		LinkedList<BigraphComponentNode> siblings = new LinkedList<BigraphComponentNode> ();
		for (BigraphComponentNode node:chains)
		{
			if ( (node.getParent()==currNode.getParent())
					&& (node.getProvidedServiceShortName().compareTo(currNode.getProvidedServiceShortName())==0))
			{
				siblings.add(node);
			}
		}
		// TODO Auto-generated method stub
		return siblings;
	}

	/**
	 * 
	 * 
	 * @return A linked list with the provision chains for each end-user service
	 */
	private static void evaluateComponents ()
	{
		Iterator <ComponentContainer>componentsItr = Repository.getComponents().iterator();
		ComponentContainer cmp;
		provisionTrees.clear();
		//TODO is there a more efficient way to evaluate? 
		//Currently all the trees are removed and reconstructed after revisiting 
		//all the end-user components in the Repository
		while (componentsItr.hasNext())
		{
			cmp = componentsItr.next();
			//If the component provides end-user services
			//It is the start point for a resolution
			if (cmp.getEndUserServices()!=null)
			{
				for (String s:cmp.getEndUserServices())
				{
					provisionTrees.add(new BigraphAdaptationTree (cmp, s));
				}
				
				
			}
		}
		
	}

	public static double getValue(String var) 
	{
		LinkedList<BigraphComponentNode> tree;
		Iterator <LinkedList<BigraphComponentNode>> itr = maxProvisionTrees.iterator();
		while (itr.hasNext())
		{
			tree = itr.next();
			for (BigraphComponentNode node:tree)
			{
				if (node==null)
					System.out.println ("node is null");
				
				if (node.getProvidedServiceShortName().compareTo(var)==0)
				{
					return Repository.getServiceWrapper(node.getProvidedService(), node.getContainer()).getUtility();
				}
			}
		}
		return -1.0;
		
	}
	
		
	
}
